﻿using System;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace PARCS.Core
{
    public class ServerQuery
    {
        public byte[] Answer { get; private set; }  //answer

        private static bool _tcpRespond = true;  //does host server works on the TCP or UDP-port
        //private static bool _udpRespond = true; //does host server works on the UDP-port

        private byte[] _data = null;	//data for query

        private BinaryWriter _dataOut = null;

        public ServerQuery(byte[] data = null)
        {
            _data = data;
        }

        /// <summary>
        /// Creates and returns stream for writing data of query
        /// </summary>
        /// <returns>Stream</returns>
        public BinaryWriter GetOutputStream()
        {
            return _dataOut = new BinaryWriter(new MemoryStream());
        }

        
        /// <summary>
        /// Creates and returns stream for writing data of query
        /// </summary>
        /// <param name="size">Stream's buffer size</param>
        /// <returns>Stream</returns>
        public BinaryWriter GetOutputStream(int size)
        {
            return _dataOut = new BinaryWriter(new MemoryStream());
        }
        
        /// <summary>
        /// Get streams with server's answer
        /// </summary>
        /// <returns>Stream</returns>
        public BinaryReader GetInputStream()
        {
            return new BinaryReader(new MemoryStream(Answer));
        }

        /// <summary>
        /// Connect to the TCO host server. If unluck - sends UDP query
        /// </summary>
        /// <param name="server">Server info</param>
        /// <returns>Server's answer or null, if no response is recieved</returns>
        public byte[] Send(HostInfo server)
        {
            return Send(server, -1);
        }

        /// <summary>
        /// Connect to the TCO host server. If unluck - sends UDP query
        /// </summary>
        /// <param name="server">Server info</param>
        /// <param name="answerLength">The expected length of the response, or -1 if unknown</param>
        /// <returns>Server's answer or null, if no response is recieved</returns>
        public byte[] Send(HostInfo server, int answerLength)
        {
            if (server == null)
            {
                Console.Out.WriteLine("Error: serv==null");
                return null;
            }

            if (_tcpRespond)
            {
                if (!(_tcpRespond = server.CycleConnect(Const.HostTCPPort)))
                {
                    Console.Out.WriteLine("TCP hosts server is not responding on " + server.Name + ":" + Const.HostTCPPort);
                }
            
                try
                {
                    if (server.Socket == null)
                    {
                        Console.Out.WriteLine("Error: socket==null");
                        return null;
                    }

                    using (var stream = new NetworkStream(server.Socket))
                    {
                        var inputStream = new BinaryReader(stream);
                        var outputStream = new BinaryWriter(stream);

                        if (_dataOut != null && _dataOut.BaseStream.Length > 0)
                        {
                            var data = (new BinaryReader(_dataOut.BaseStream)).ReadBytes((int) _dataOut.BaseStream.Length);
                            outputStream.Write(data);
                        }
                        else  if (_data != null)
                        {
                            outputStream.Write(_data);
                            outputStream.Flush();
                        }
                        else
                        {
                            Console.Out.WriteLine("Error: ServerQuery._data == null");
                            return null;
                        }



                        if (answerLength == 0)
                        {
                            server.CloseSocket();
                            return null;
                        }
                        else if (answerLength != -1)
                        {
                            Answer = new byte[answerLength];
                        }

                        Answer = inputStream.ReadBytes(answerLength);
                    }
                }
                catch (Exception e)
                {
                    Console.Out.WriteLine(e.Message);
                    return null;
                }
                finally
                {
                    server.CloseSocket();
                }
            }

            if (!_tcpRespond)
            {
                using (var udpClient = new UdpClient())
                {
                    udpClient.Connect(server.Address, Const.HostUDPPort);

                    if (_dataOut != null && _dataOut.BaseStream.Length > 0)
                    {
                        _data = (new BinaryReader(_dataOut.BaseStream)).ReadBytes((int)_dataOut.BaseStream.Length);
                    }

                    try
                    {
                        udpClient.Send(_data, _data.Length);
                    }
                    catch (Exception e)
                    {
                        Console.Out.WriteLine("UDP hosts server is not responding on " + server + ":" +
                                              Const.HostUDPPort);
                        _tcpRespond = true;
                        return null;
                    }

                    if (answerLength == 0) return null;

                    var ipEndPoint = new IPEndPoint(IPAddress.Any, 0);

                    Answer = udpClient.Receive(ref ipEndPoint);

                    if ((answerLength != -1) && (answerLength != Answer.Length))
                    {
                        Console.Out.WriteLine("The answer from hosts server is invalid");
                        return null;
                    }
                }
            }
            return Answer;
        }
    }
}